package main

import (
	"bytes"
	_ "crypto/sha512"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"os"
	"os/user"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/mattn/go-colorable"
	"github.com/klauspost/lctime"
	"github.com/mattn/go-runewidth"
)

type configuration struct {
	APIKey   string
	City     string
	Numdays  int
	Imperial bool
    WindUnit bool
	Inverse  bool
	Lang     string
    Narrow   bool
    LocationName string
    WindMS  bool
    RightToLeft bool
}

type cond struct {
	ChanceOfRain   string  `json:"chanceofrain"`
	FeelsLikeC     int     `json:",string"`
	PrecipMM       float32 `json:"precipMM,string"`
	TempC          int     `json:"tempC,string"`
	TempC2         int     `json:"temp_C,string"`
	Time           int     `json:"time,string"`
	VisibleDistKM  int     `json:"visibility,string"`
	WeatherCode    int     `json:"weatherCode,string"`
	WeatherDesc    []struct{ Value string }
	WindGustKmph   int `json:",string"`
	Winddir16Point string
	WindspeedKmph  int `json:"windspeedKmph,string"`
}

type astro struct {
	Moonrise string
	Moonset  string
	Sunrise  string
	Sunset   string
}

type weather struct {
	Astronomy []astro
	Date      string
	Hourly    []cond
	MaxtempC  int `json:"maxtempC,string"`
	MintempC  int `json:"mintempC,string"`
}

type loc struct {
	Query string `json:"query"`
	Type  string `json:"type"`
}

type resp struct {
	Data struct {
		Cur     []cond                 `json:"current_condition"`
		Err     []struct{ Msg string } `json:"error"`
		Req     []loc                  `json:"request"`
		Weather []weather              `json:"weather"`
	} `json:"data"`
}

var (
	ansiEsc    *regexp.Regexp
	config     configuration
	configpath string
	debug      bool
	windDir    = map[string]string{
		"N":   "\033[1m↓\033[0m",
		"NNE": "\033[1m↓\033[0m",
		"NE":  "\033[1m↙\033[0m",
		"ENE": "\033[1m↙\033[0m",
		"E":   "\033[1m←\033[0m",
		"ESE": "\033[1m←\033[0m",
		"SE":  "\033[1m↖\033[0m",
		"SSE": "\033[1m↖\033[0m",
		"S":   "\033[1m↑\033[0m",
		"SSW": "\033[1m↑\033[0m",
		"SW":  "\033[1m↗\033[0m",
		"WSW": "\033[1m↗\033[0m",
		"W":   "\033[1m→\033[0m",
		"WNW": "\033[1m→\033[0m",
		"NW":  "\033[1m↘\033[0m",
		"NNW": "\033[1m↘\033[0m",
	}
	unitRain = map[bool]string{
		false: "mm",
		true:  "in",
	}
	unitTemp = map[bool]string{
		false: "C",
		true:  "F",
	}
	unitVis = map[bool]string{
		false: "km",
		true:  "mi",
	}
	unitWind = map[int]string{
		0: "km/h",
		1: "mph",
		2: "m/s",
	}
	slotTimes = [slotcount]int{9 * 60, 12 * 60, 18 * 60, 22 * 60}
	codes     = map[int][]string{
		113: iconSunny,
		116: iconPartlyCloudy,
		119: iconCloudy,
		122: iconVeryCloudy,
		143: iconFog,
		176: iconLightShowers,
		179: iconLightSleetShowers,
		182: iconLightSleet,
		185: iconLightSleet,
		200: iconThunderyShowers,
		227: iconLightSnow,
		230: iconHeavySnow,
		248: iconFog,
		260: iconFog,
		263: iconLightShowers,
		266: iconLightRain,
		281: iconLightSleet,
		284: iconLightSleet,
		293: iconLightRain,
		296: iconLightRain,
		299: iconHeavyShowers,
		302: iconHeavyRain,
		305: iconHeavyShowers,
		308: iconHeavyRain,
		311: iconLightSleet,
		314: iconLightSleet,
		317: iconLightSleet,
		320: iconLightSnow,
		323: iconLightSnowShowers,
		326: iconLightSnowShowers,
		329: iconHeavySnow,
		332: iconHeavySnow,
		335: iconHeavySnowShowers,
		338: iconHeavySnow,
		350: iconLightSleet,
		353: iconLightShowers,
		356: iconHeavyShowers,
		359: iconHeavyRain,
		362: iconLightSleetShowers,
		365: iconLightSleetShowers,
		368: iconLightSnowShowers,
		371: iconHeavySnowShowers,
		374: iconLightSleetShowers,
		377: iconLightSleet,
		386: iconThunderyShowers,
		389: iconThunderyHeavyRain,
		392: iconThunderySnowShowers,
		395: iconHeavySnowShowers,
	}

	iconUnknown = []string{
		"    .-.      ",
		"     __)     ",
		"    (        ",
		"     `-’     ",
		"      •      "}
	iconSunny = []string{
		"\033[38;5;226m    \\   /    \033[0m",
		"\033[38;5;226m     .-.     \033[0m",
		"\033[38;5;226m  ― (   ) ―  \033[0m",
		"\033[38;5;226m     `-’     \033[0m",
		"\033[38;5;226m    /   \\    \033[0m"}
	iconPartlyCloudy = []string{
		"\033[38;5;226m   \\  /\033[0m      ",
		"\033[38;5;226m _ /\"\"\033[38;5;250m.-.    \033[0m",
		"\033[38;5;226m   \\_\033[38;5;250m(   ).  \033[0m",
		"\033[38;5;226m   /\033[38;5;250m(___(__) \033[0m",
		"             "}
	iconCloudy = []string{
		"             ",
		"\033[38;5;250m     .--.    \033[0m",
		"\033[38;5;250m  .-(    ).  \033[0m",
		"\033[38;5;250m (___.__)__) \033[0m",
		"             "}
	iconVeryCloudy = []string{
		"             ",
		"\033[38;5;240;1m     .--.    \033[0m",
		"\033[38;5;240;1m  .-(    ).  \033[0m",
		"\033[38;5;240;1m (___.__)__) \033[0m",
		"             "}
	iconLightShowers = []string{
		"\033[38;5;226m _`/\"\"\033[38;5;250m.-.    \033[0m",
		"\033[38;5;226m  ,\\_\033[38;5;250m(   ).  \033[0m",
		"\033[38;5;226m   /\033[38;5;250m(___(__) \033[0m",
		"\033[38;5;111m     ‘ ‘ ‘ ‘ \033[0m",
		"\033[38;5;111m    ‘ ‘ ‘ ‘  \033[0m"}
	iconHeavyShowers = []string{
		"\033[38;5;226m _`/\"\"\033[38;5;240;1m.-.    \033[0m",
		"\033[38;5;226m  ,\\_\033[38;5;240;1m(   ).  \033[0m",
		"\033[38;5;226m   /\033[38;5;240;1m(___(__) \033[0m",
		"\033[38;5;21;1m   ‚‘‚‘‚‘‚‘  \033[0m",
		"\033[38;5;21;1m   ‚’‚’‚’‚’  \033[0m"}
	iconLightSnowShowers = []string{
		"\033[38;5;226m _`/\"\"\033[38;5;250m.-.    \033[0m",
		"\033[38;5;226m  ,\\_\033[38;5;250m(   ).  \033[0m",
		"\033[38;5;226m   /\033[38;5;250m(___(__) \033[0m",
		"\033[38;5;255m     *  *  * \033[0m",
		"\033[38;5;255m    *  *  *  \033[0m"}
	iconHeavySnowShowers = []string{
		"\033[38;5;226m _`/\"\"\033[38;5;240;1m.-.    \033[0m",
		"\033[38;5;226m  ,\\_\033[38;5;240;1m(   ).  \033[0m",
		"\033[38;5;226m   /\033[38;5;240;1m(___(__) \033[0m",
		"\033[38;5;255;1m    * * * *  \033[0m",
		"\033[38;5;255;1m   * * * *   \033[0m"}
	iconLightSleetShowers = []string{
		"\033[38;5;226m _`/\"\"\033[38;5;250m.-.    \033[0m",
		"\033[38;5;226m  ,\\_\033[38;5;250m(   ).  \033[0m",
		"\033[38;5;226m   /\033[38;5;250m(___(__) \033[0m",
		"\033[38;5;111m     ‘ \033[38;5;255m*\033[38;5;111m ‘ \033[38;5;255m* \033[0m",
		"\033[38;5;255m    *\033[38;5;111m ‘ \033[38;5;255m*\033[38;5;111m ‘  \033[0m"}
	iconThunderyShowers = []string{
		"\033[38;5;226m _`/\"\"\033[38;5;250m.-.    \033[0m",
		"\033[38;5;226m  ,\\_\033[38;5;250m(   ).  \033[0m",
		"\033[38;5;226m   /\033[38;5;250m(___(__) \033[0m",
		"\033[38;5;228;5m    ⚡\033[38;5;111;25m‘‘\033[38;5;228;5m⚡\033[38;5;111;25m‘‘ \033[0m",
		"\033[38;5;111m    ‘ ‘ ‘ ‘  \033[0m"}
	iconThunderyHeavyRain = []string{
		"\033[38;5;240;1m     .-.     \033[0m",
		"\033[38;5;240;1m    (   ).   \033[0m",
		"\033[38;5;240;1m   (___(__)  \033[0m",
		"\033[38;5;21;1m  ‚‘\033[38;5;228;5m⚡\033[38;5;21;25m‘‚\033[38;5;228;5m⚡\033[38;5;21;25m‚‘ \033[0m",
		"\033[38;5;21;1m  ‚’‚’\033[38;5;228;5m⚡\033[38;5;21;25m’‚’  \033[0m"}
	iconThunderySnowShowers = []string{
		"\033[38;5;226m _`/\"\"\033[38;5;250m.-.    \033[0m",
		"\033[38;5;226m  ,\\_\033[38;5;250m(   ).  \033[0m",
		"\033[38;5;226m   /\033[38;5;250m(___(__) \033[0m",
		"\033[38;5;255m     *\033[38;5;228;5m⚡\033[38;5;255;25m*\033[38;5;228;5m⚡\033[38;5;255;25m* \033[0m",
		"\033[38;5;255m    *  *  *  \033[0m"}
	iconLightRain = []string{
		"\033[38;5;250m     .-.     \033[0m",
		"\033[38;5;250m    (   ).   \033[0m",
		"\033[38;5;250m   (___(__)  \033[0m",
		"\033[38;5;111m    ‘ ‘ ‘ ‘  \033[0m",
		"\033[38;5;111m   ‘ ‘ ‘ ‘   \033[0m"}
	iconHeavyRain = []string{
		"\033[38;5;240;1m     .-.     \033[0m",
		"\033[38;5;240;1m    (   ).   \033[0m",
		"\033[38;5;240;1m   (___(__)  \033[0m",
		"\033[38;5;21;1m  ‚‘‚‘‚‘‚‘   \033[0m",
		"\033[38;5;21;1m  ‚’‚’‚’‚’   \033[0m"}
	iconLightSnow = []string{
		"\033[38;5;250m     .-.     \033[0m",
		"\033[38;5;250m    (   ).   \033[0m",
		"\033[38;5;250m   (___(__)  \033[0m",
		"\033[38;5;255m    *  *  *  \033[0m",
		"\033[38;5;255m   *  *  *   \033[0m"}
	iconHeavySnow = []string{
		"\033[38;5;240;1m     .-.     \033[0m",
		"\033[38;5;240;1m    (   ).   \033[0m",
		"\033[38;5;240;1m   (___(__)  \033[0m",
		"\033[38;5;255;1m   * * * *   \033[0m",
		"\033[38;5;255;1m  * * * *    \033[0m"}
	iconLightSleet = []string{
		"\033[38;5;250m     .-.     \033[0m",
		"\033[38;5;250m    (   ).   \033[0m",
		"\033[38;5;250m   (___(__)  \033[0m",
		"\033[38;5;111m    ‘ \033[38;5;255m*\033[38;5;111m ‘ \033[38;5;255m*  \033[0m",
		"\033[38;5;255m   *\033[38;5;111m ‘ \033[38;5;255m*\033[38;5;111m ‘   \033[0m"}
	iconFog = []string{
		"             ",
		"\033[38;5;251m _ - _ - _ - \033[0m",
		"\033[38;5;251m  _ - _ - _  \033[0m",
		"\033[38;5;251m _ - _ - _ - \033[0m",
		"             "}

    locale = map[string]string{
        "af":   "af_ZA",
        "ar":   "ar_TN",
        "az":   "az_AZ",
        "be":   "be_BY",
        "bg":   "bg_BG",
        "bs":   "bs_BA",
        "ca":   "ca_ES",
        "cs":   "cs_CZ",
        "cy":   "cy_GB",
        "da":   "da_DK",
        "de":   "de_DE",
        "el":   "el_GR",
        "eo":   "eo",
        "es":   "es_ES",
        "et":   "et_EE",
        "fa":   "fa_IR",
        "fi":   "fi_FI",
        "fr":   "fr_FR",
        "fy":   "fy_NL",
        "ga":   "ga_IE",
        "he":   "he_IL",
        "hr":   "hr_HR",
        "hu":   "hu_HU",
        "hy":   "hy_AM",
        "ia":   "ia",
        "id":   "id_ID",
        "is":   "is_IS",
        "it":   "it_IT",
        "ja":   "ja_JP",
        "jv":   "en_US",
        "ka":   "ka_GE",
        "ko":   "ko_KR",
        "kk":   "kk_KZ",
        "ky":   "ky_KG",
        "lt":   "lt_LT",
        "lv":   "lv_LV",
        "mk":   "mk_MK",
        "ml":   "ml_IN",
        "nb":   "nb_NO",
        "nl":   "nl_NL",
        "nn":   "nn_NO",
        "pt":   "pt_PT",
        "pt-br":"pt_BR",
        "pl":   "pl_PL",
        "ro":   "ro_RO",
        "ru":   "ru_RU",
        "sv":   "sv_SE",
        "sk":   "sk_SK",
        "sl":   "sl_SI",
        "sr":   "sr_RS",
        "sr-lat":   "sr_RS@latin",
        "sw":   "sw_KE",
        "th":   "th_TH",
        "tr":   "tr_TR",
        "uk":   "uk_UA",
        "uz":   "uz_UZ",
        "vi":   "vi_VN",
        "zu":   "zu_ZA",
        "zh":   "zh_CN",
        "zh-cn":"zh_CN",
        "zh-tw":"zh_TW",
    }

    localizedCaption = map[string]string{
        "af": "Weer verslag vir:",
        "ar": "تقرير حالة ألطقس",
        "az": "Hava proqnozu:",
        "be": "Прагноз надвор'я для:",
        "bg": "Прогноза за времето в:",
        "bs": "Vremenske prognoze za:",
        "ca": "Informe del temps per a:",
        "cs": "Předpověď počasí pro:",
        "cy": "Adroddiad tywydd ar gyfer:",
        "da": "Vejret i:",
        "de": "Wetterbericht für:",
        "el": "Πρόγνωση καιρού για:",
        "eo": "Veterprognozo por:",
        "es": "El tiempo en:",
        "et": "Ilmaprognoos:",
        "fa": "اوه و بآ تیعضو شرازگ",
        "fi": "Säätiedotus:",
        "fr": "Prévisions météo pour:",
        "fy": "Waarberjocht foar:",
        "ga": "Réamhaisnéis na haimsire do:",
        "he": ":ריוואה גזמ תיזחת",
        "hr": "Vremenska prognoza za:",
        "hu": "Időjárás előrejelzés:",
        "hy": "Եղանակի տեսություն:",
        "ia": "Le tempore a:",
        "id": "Prakiraan cuaca:",
        "it": "Previsioni meteo:",
        "is": "Veðurskýrsla fyrir:",
        "ja": "天気予報：",
        "jv": "Weather forecast for:",
        "ka": "ამინდის პროგნოზი:",
        "kk": "Ауа райы:",
        "ko": "일기 예보：",
        "ky": "Аба ырайы:",
        "lt": "Orų prognozė:",
        "lv": "Laika ziņas:",
        "mk": "Прогноза за времето во:",
        "ml": "കാലാവസ്ഥ റിപ്പോർട്ട്:",
        "nb": "Værmelding for:",
        "nl": "Weerbericht voor:",
        "nn": "Vêrmelding for:",
        "pl": "Pogoda w:",
        "pt": "Previsão do tempo para:",
        "pt-br": "Previsão do tempo para:",
        "ro": "Prognoza meteo pentru:",
        "ru": "Прогноз погоды:",
        "sk": "Predpoveď počasia pre:",
        "sl": "Vremenska napoved za",
        "sr": "Временска прогноза за:",
        "sr-lat":"Vremenska prognoza za:",
        "sv": "Väderleksprognos för:",
        "sw": "Ripoti ya hali ya hewa, jiji la:",
        "te": "వాతావరణ సమాచారము:",
        "th": "รายงานสภาพอากาศ:",
        "tr": "Hava beklentisi:",
        "uk": "Прогноз погоди для:",
        "uz": "Ob-havo bashorati:",
        "vi": "Báo cáo thời tiết:",
        "zu": "Isimo sezulu:",
        "zh": "天气预报：",
        "zh-cn": "天气预报：",
        "zh-tw": "天氣預報：",
    }

    daytimeTranslation = map[string][]string{
        "af":{"Oggend","Middag", "Vroegaand", "Laatnag"},
        "ar":{"صباح",   "ظهر",      "مساء",     "ليل"   },
        "az":{"Səhər",  "Gün",      "Axşam",    "Gecə"  },
        "be":{"Раніца", "Дзень",    "Вечар",    "Ноч"   },
        "bg":{"Сутрин", "Обяд",     "Вечер",    "Нощ"   },
        "bs":{"Ujutro", "Dan",      "Večer",    "Noć"   },
        "cs":{"Ráno",   "Ve dne",   "Večer",    "V noci"},
        "ca":{"Matí",   "Dia",      "Tarda",    "Nit"   },
        "cy":{"Bore",   "Dydd",     "Hwyr",     "Nos"   },
        "da":{"Morgen", "Middag",   "Aften",    "Nat"   },
        "de":{"Früh",   "Mittag",   "Abend",    "Nacht" },
        "el":{"Πρωί",   "Μεσημέρι", "Απόγευμα", "Βράδυ" },
        "en":{"Morning","Noon",     "Evening",  "Night" },
        "eo":{"Mateno", "Tago",     "Vespero",  "Nokto" },
        "es":{"Mañana", "Día",      "Tarde",    "Noche" },
        "et":{"Hommik", "Päev",     "Õhtu",     "Öösel" },
        "fa":{    "حبص",     "رهظ",      "رصع",   "بش"  },
        "fi":{"Aamu",   "Keskipäivä", "Ilta",   "Yö"    },
        "fr":{"Matin",  "Après-midi", "Soir",   "Nuit"  },
        "fy":{"Moarns",  "Middeis", "Jûns",     "Nachts"},
        "ga":{"Maidin",  "Nóin",    "Tráthnóna","Oíche"},
        "he":{ "רקוב",  "םוֹיְ",      "ברֶעֶ",      "הלָיְלַ"  },
        "hr":{"Jutro",  "Dan",      "Večer",    "Noć"   },
        "hu":{"Reggel", "Dél",      "Este",     "Éjszaka"},
        "hy":{"Առավոտ", "Կեսօր",    "Երեկո",    "Գիշեր" },
        "ia":{"Matino", "Mediedie", "Vespere", "Nocte"},
        "id":{"Pagi",   "Hari",     "Petang",   "Malam" },
        "it":{"Mattina","Pomeriggio", "Sera",   "Notte" },
        "is":{"Morgunn","Dagur",    "Kvöld",    "Nótt"  },
        "ja":{"朝",     "昼",       "夕",       "夜"    },
        "jv":{"Morning","Noon",     "Evening",  "Night" },
        "ka":{"დილა",   "დღე",      "საღამო",   "ღამე"  },
        "kk":{"Таң",    "Күндіз",   "Кеш",      "Түн"   },
        "ko":{"아침",   "낮",       "저녁",     "밤"    },
        "ky":{"Эртең",  "Күн",      "Кеч",      "Түн"   },
        "lt":{"Rytas",  "Diena",    "Vakaras",  "Naktis"},
        "lv":{"Rīts",   "Diena",    "Vakars",   "Nakts" },
        "mk":{"Утро",   "Пладне",   "Вечер",    "Ноќ"   },
        "ml":{ "മോണിംഗ്", "മധ്യാഹ്നം", "വൈകുന്നേരം", "രാത്രി",},
        "nl":{"'s Ochtends","'s Middags", "'s Avonds", "'s Nachts"},
        "nb":{"Morgen", "Middag",   "Kveld",    "Natt"  },
        "nn":{"Morgon", "Middag",   "Kveld",    "Natt"  },
        "pl":{"Ranek",  "Dzień",    "Wieczór",  "Noc"   },
        "pt":{"Manhã",  "Meio-dia", "Tarde",    "Noite" },
        "pt-br":{"Manhã",  "Meio-dia", "Tarde",    "Noite" },
        "ro":{"Dimineaţă","Amiază", "Seară",    "Noapte"},
        "ru":{"Утро",   "День",     "Вечер",    "Ночь"  },
        "sk":{"Ráno",   "Cez deň",  "Večer",    "V noci"},
        "sl":{"Jutro",  "Dan",      "Večer",    "Noč"   },
        "sr":{"Јутро",  "Подне",    "Вече",     "Ноћ"   },
        "sr-lat":{"Jutro", "Podne", "Veče",     "Noć"   },
        "sv":{"Morgon", "Eftermiddag","Kväll",  "Natt"  },
        "sw":{"Asubuhi","Adhuhuri", "Jioni",    "Usiku" },
        "te":{"ఉదయం", "రోజు", "సాయంత్రం", "రాత్రి" },
        "th":{"เช้า",    "วัน",       "เย็น",      "คืน"    },
        "tr":{"Sabah",  "Öğle",     "Akşam",    "Gece"  },
        "uk":{"Ранок",  "День",     "Вечір",    "Ніч"   },
        "uz":{"Ertalab","Kunduzi",  "Kechqurun","Kecha" },
        "vi":{"Sáng",   "Trưa",     "Chiều",    "Tối"   },
        "zh":   {"早上",   "中午",     "傍晚",     "夜间"    },
        "zh-cn":{"早上",   "中午",     "傍晚",     "夜间"    },
        "zh-tw":{"早上",   "中午",     "傍晚",     "夜間"    },
        "zu":{"Morning","Noon",     "Evening",  "Night" },
    }
)

// Add this languages:
// da tr hu sr jv zu
// More languages: https://developer.worldweatheronline.com/api/multilingual.aspx

// const (
// 	wuri      = "https://api.worldweatheronline.com/premium/v1/weather.ashx?"
// 	suri      = "https://api.worldweatheronline.com/premium/v1/search.ashx?"
// 	slotcount = 4
// )

const (
    wuri      = "http://127.0.0.1:5001/premium/v1/weather.ashx?"
    suri      = "http://127.0.0.1:5001/premium/v1/search.ashx?"
    slotcount = 4
)

func configload() error {
	b, err := ioutil.ReadFile(configpath)
	if err == nil {
		return json.Unmarshal(b, &config)
	}
	return err
}

func configsave() error {
	j, err := json.MarshalIndent(config, "", "\t")
	if err == nil {
		return ioutil.WriteFile(configpath, j, 0600)
	}
	return err
}

func pad(s string, mustLen int) (ret string) {
	ret = s
	realLen := utf8.RuneCountInString(ansiEsc.ReplaceAllLiteralString(s, ""))
	delta := mustLen - realLen
	if delta > 0 {
        if config.RightToLeft {
            ret = strings.Repeat(" ", delta) + ret + "\033[0m"
        } else {
            ret += "\033[0m" + strings.Repeat(" ", delta)
        }
	} else if delta < 0 {
		toks := ansiEsc.Split(s, 2)
		tokLen := utf8.RuneCountInString(toks[0])
		esc := ansiEsc.FindString(s)
		if tokLen > mustLen {
			ret = fmt.Sprintf("%.*s\033[0m", mustLen, toks[0])
		} else {
			ret = fmt.Sprintf("%s%s%s", toks[0], esc, pad(toks[1], mustLen-tokLen))
		}
	}
	return
}

func formatTemp(c cond) string {
	color := func(temp int, explicit_plus bool) string {
        var col = 0
        if !config.Inverse {
            col = 21
            switch temp {
            case -15, -14, -13:
                col = 27
            case -12, -11, -10:
                col = 33
            case -9, -8, -7:
                col = 39
            case -6, -5, -4:
                col = 45
            case -3, -2, -1:
                col = 51
            case 0, 1:
                col = 50
            case 2, 3:
                col = 49
            case 4, 5:
                col = 48
            case 6, 7:
                col = 47
            case 8, 9:
                col = 46
            case 10, 11, 12:
                col = 82
            case 13, 14, 15:
                col = 118
            case 16, 17, 18:
                col = 154
            case 19, 20, 21:
                col = 190
            case 22, 23, 24:
                col = 226
            case 25, 26, 27:
                col = 220
            case 28, 29, 30:
                col = 214
            case 31, 32, 33:
                col = 208
            case 34, 35, 36:
                col = 202
            default:
                if temp > 0 {
                    col = 196
                }
            }
        } else {
            col = 16
            switch temp {
            case -15, -14, -13:
                col = 17
            case -12, -11, -10:
                col = 18
            case -9, -8, -7:
                col = 19
            case -6, -5, -4:
                col = 20
            case -3, -2, -1:
                col = 21
            case 0, 1:
                col = 30
            case 2, 3:
                col = 28
            case 4, 5:
                col = 29
            case 6, 7:
                col = 30
            case 8, 9:
                col = 34
            case 10, 11, 12:
                col = 35
            case 13, 14, 15:
                col = 36
            case 16, 17, 18:
                col = 40
            case 19, 20, 21:
                col = 59
            case 22, 23, 24:
                col = 100
            case 25, 26, 27:
                col = 101
            case 28, 29, 30:
                col = 94
            case 31, 32, 33:
                col = 166
            case 34, 35, 36:
                col = 52
            default:
                if temp > 0 {
                    col = 196
                }
            }
        }
		if config.Imperial {
			temp = (temp*18 + 320) / 10
		}
                if explicit_plus {
                    return fmt.Sprintf("\033[38;5;%03dm+%d\033[0m", col, temp)
                } else {
                    return fmt.Sprintf("\033[38;5;%03dm%d\033[0m", col, temp)
                }
	}
	t := c.TempC
	if t == 0 {
		t = c.TempC2
	}

	hyphen := " - "
    // if (config.Lang == "sl") {
    //     hyphen = "-"
    // }
	hyphen = ".."

        explicit_plus := false
	if c.FeelsLikeC < t {
                if c.FeelsLikeC < 0 && t > 0 {
                    explicit_plus = true
                }
		return pad(fmt.Sprintf("%s%s%s °%s", color(c.FeelsLikeC, false), hyphen, color(t, explicit_plus), unitTemp[config.Imperial]), 15)
	} else if c.FeelsLikeC > t {
                if t < 0 && c.FeelsLikeC > 0 {
                    explicit_plus = true
                }
		return pad(fmt.Sprintf("%s%s%s °%s", color(t, false), hyphen, color(c.FeelsLikeC, explicit_plus), unitTemp[config.Imperial]), 15)
	}
	return pad(fmt.Sprintf("%s °%s", color(c.FeelsLikeC, false), unitTemp[config.Imperial]), 15)
}

func formatWind(c cond) string {
    windInRightUnits := func(spd int) int {
        if config.WindMS {
            spd = (spd * 1000) / 3600
        } else {
            if config.Imperial {
                spd = (spd * 1000) / 1609
            }
        }
        return spd
    }
	color := func(spd int) string {
		var col = 46
		switch spd {
		case 1, 2, 3:
			col = 82
		case 4, 5, 6:
			col = 118
		case 7, 8, 9:
			col = 154
		case 10, 11, 12:
			col = 190
		case 13, 14, 15:
			col = 226
		case 16, 17, 18, 19:
			col = 220
		case 20, 21, 22, 23:
			col = 214
		case 24, 25, 26, 27:
			col = 208
		case 28, 29, 30, 31:
			col = 202
		default:
			if spd > 0 {
				col = 196
			}
		}
        spd = windInRightUnits(spd)

		return fmt.Sprintf("\033[38;5;%03dm%d\033[0m", col, spd)
	}

    unitWindString := unitWind[0]
    if config.WindMS {
        unitWindString = unitWind[2]
    } else {
        if config.Imperial {
            unitWindString = unitWind[1]
        }
    }

    hyphen := " - "
    // if (config.Lang == "sl") {
    //     hyphen = "-"
    // }
    hyphen = "-"

    cWindGustKmph := fmt.Sprintf("%s", color(c.WindGustKmph))
    cWindspeedKmph := fmt.Sprintf("%s", color(c.WindspeedKmph))
	if windInRightUnits(c.WindGustKmph) > windInRightUnits(c.WindspeedKmph) {
		return pad(fmt.Sprintf("%s %s%s%s %s", windDir[c.Winddir16Point], cWindspeedKmph, hyphen, cWindGustKmph, unitWindString), 15)
	}
	return pad(fmt.Sprintf("%s %s %s", windDir[c.Winddir16Point], cWindspeedKmph, unitWindString), 15)
}

func formatVisibility(c cond) string {
	if config.Imperial {
		c.VisibleDistKM = (c.VisibleDistKM * 621) / 1000
	}
	return pad(fmt.Sprintf("%d %s", c.VisibleDistKM, unitVis[config.Imperial]), 15)
}

func formatRain(c cond) string {
	rainUnit := float32(c.PrecipMM)
	if config.Imperial {
		rainUnit = float32(c.PrecipMM) * 0.039
	}
	if c.ChanceOfRain != "" {
		return pad(fmt.Sprintf("%.1f %s | %s%%", rainUnit, unitRain[config.Imperial], c.ChanceOfRain), 15)
	}
	return pad(fmt.Sprintf("%.1f %s", rainUnit, unitRain[config.Imperial]), 15)
}

func formatCond(cur []string, c cond, current bool) (ret []string) {
	var icon []string
	if i, ok := codes[c.WeatherCode]; !ok {
		icon = iconUnknown
	} else {
		icon = i
	}
    if config.Inverse {
        // inverting colors
        for i, _ := range icon {
            icon[i] = strings.Replace(icon[i], "38;5;226",  "38;5;94", -1)
            icon[i] = strings.Replace(icon[i], "38;5;250",  "38;5;243", -1)
            icon[i] = strings.Replace(icon[i], "38;5;21",   "38;5;18", -1)
            icon[i] = strings.Replace(icon[i], "38;5;255",  "38;5;245", -1)
            icon[i] = strings.Replace(icon[i], "38;5;111",  "38;5;63", -1)
            icon[i] = strings.Replace(icon[i], "38;5;251",  "38;5;238", -1)
        }
    }
	//desc := fmt.Sprintf("%-15.15v", c.WeatherDesc[0].Value)
	desc := c.WeatherDesc[0].Value
    if config.RightToLeft {
        for runewidth.StringWidth(desc) < 15 {
            desc = " " + desc
        }
        for runewidth.StringWidth(desc) > 15 {
            _, size := utf8.DecodeLastRuneInString(desc)
            desc = desc[size:len(desc)]
        }
    } else {
        for runewidth.StringWidth(desc) < 15 {
            desc += " "
        }
        for runewidth.StringWidth(desc) > 15 {
            _, size := utf8.DecodeLastRuneInString(desc)
            desc = desc[:len(desc)-size]
        }
    }
	if current {
        if config.RightToLeft {
            desc = c.WeatherDesc[0].Value
            if runewidth.StringWidth(desc) < 15 {
                desc = strings.Repeat(" ", 15 - runewidth.StringWidth(desc)) + desc
            }
        } else {
            desc = c.WeatherDesc[0].Value
        }
	} else {
        if config.RightToLeft {
            if frstRune, size := utf8.DecodeRuneInString(desc); frstRune != ' ' {
                desc = "…" + desc[size:len(desc)]
                for runewidth.StringWidth(desc) < 15 {
                    desc = " " + desc
                }
            }
        } else {
            if lastRune, size := utf8.DecodeLastRuneInString(desc); lastRune != ' ' {
                desc = desc[:len(desc)-size] + "…"
                //for numberOfSpaces < runewidth.StringWidth(fmt.Sprintf("%c", lastRune)) - 1 {
                for runewidth.StringWidth(desc) < 15 {
                    desc = desc + " "
                }
            }
        }
	}
    if (config.RightToLeft) {
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[0], desc,          icon[0]))
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[1], formatTemp(c), icon[1]))
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[2], formatWind(c), icon[2]))
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[3], formatVisibility(c), icon[3]))
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[4], formatRain(c), icon[4]))
    } else {
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[0], icon[0], desc))
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[1], icon[1], formatTemp(c)))
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[2], icon[2], formatWind(c)))
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[3], icon[3], formatVisibility(c)))
        ret = append(ret, fmt.Sprintf("%v %v %v", cur[4], icon[4], formatRain(c)))
    }
	return
}

func justifyCenter(s string, width int) string {
    appendSide := 0
    for runewidth.StringWidth(s) <= width {
        if appendSide == 1 {
            s = s + " "
            appendSide = 0
        } else {
            s = " " + s
            appendSide = 1
        }
    }
    return s
}

func reverse(s string) string {
	r := []rune(s)
	for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return string(r)
}

func printDay(w weather) (ret []string) {
	hourly := w.Hourly
	ret = make([]string, 5)
	for i := range ret {
		ret[i] = "│"
	}

	// find hourly data which fits the desired times of day best
	var slots [slotcount]cond
	for _, h := range hourly {
		c := int(math.Mod(float64(h.Time), 100)) + 60*(h.Time/100)
		for i, s := range slots {
			if math.Abs(float64(c-slotTimes[i])) < math.Abs(float64(s.Time-slotTimes[i])) {
				h.Time = c
				slots[i] = h
			}
		}
	}

    if config.RightToLeft {
        slots[0], slots[3] = slots[3], slots[0]
        slots[1], slots[2] = slots[2], slots[1]
    }

	for i, s := range slots {
        if config.Narrow {
            if i == 0 || i == 2 {
                continue;
            }
        }
        ret = formatCond(ret, s, false)
		for i := range ret {
			ret[i] = ret[i] + "│"
		}
	}

	d, _ := time.Parse("2006-01-02", w.Date)
	// dateFmt := "┤ " + d.Format("Mon 02. Jan") + " ├"

    if val, ok := locale[config.Lang]; ok {
        lctime.SetLocale(val)
    } else {
        lctime.SetLocale("en_US")
    }
    dateName := ""
    if config.RightToLeft {
        dow := lctime.Strftime("%a", d)
        day := lctime.Strftime("%d", d)
        month := lctime.Strftime("%b", d)
        dateName = reverse(month) + " " + day + " " + reverse(dow)
    } else {
        dateName = lctime.Strftime("%a %d %b", d)
        if config.Lang == "ko" {
            dateName = lctime.Strftime("%b %d일 %a", d)
        }
    }
    // appendSide := 0
    // // for utf8.RuneCountInString(dateName) <= dateWidth {
    // for runewidth.StringWidth(dateName) <= dateWidth {
    //     if appendSide == 1 {
    //         dateName = dateName + " "
    //         appendSide = 0
    //     } else {
    //         dateName = " " + dateName
    //         appendSide = 1
    //     }
    // }

	dateFmt := "┤" + justifyCenter(dateName, 12) + "├"

    trans := daytimeTranslation["en"]
    if t, ok := daytimeTranslation[config.Lang]; ok {
        trans = t
    }
    if (config.Narrow) {

        names := "│      "+ justifyCenter(trans[1], 16) +
                 "└──────┬──────┘" +justifyCenter(trans[3], 16) + "      │"

        ret = append([]string{
            "                        ┌─────────────┐                        ",
            "┌───────────────────────" + dateFmt + "───────────────────────┐",
            names,
            "├──────────────────────────────┼──────────────────────────────┤"},
            ret...)

        return append(ret,
            "└──────────────────────────────┴──────────────────────────────┘")

    } else {

        names := ""
        if config.RightToLeft {
            names = "│"+ justifyCenter(trans[3], 29) + "│      "+ justifyCenter(trans[2], 16) +
                     "└──────┬──────┘" +justifyCenter(trans[1], 16) + "      │"+justifyCenter(trans[0], 29)+"│"
        } else {
            names = "│"+ justifyCenter(trans[0], 29) + "│      "+ justifyCenter(trans[1], 16) +
                     "└──────┬──────┘" +justifyCenter(trans[2], 16) + "      │"+justifyCenter(trans[3], 29)+"│"
        }

        ret = append([]string{
            "                                                       ┌─────────────┐                                                       ",
            "┌──────────────────────────────┬───────────────────────" + dateFmt + "───────────────────────┬──────────────────────────────┐",
            names,
            "├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤"},
            ret...)

        return append(ret,
            "└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘")
    }
	return
}

func unmarshalLang(body []byte, r *resp) error {
	var rv map[string]interface{}
	if err := json.Unmarshal(body, &rv); err != nil {
		return err
	}
	if data, ok := rv["data"].(map[string]interface{}); ok {
		if ccs, ok := data["current_condition"].([]interface{}); ok {
			for _, cci := range ccs {
				cc, ok := cci.(map[string]interface{})
				if !ok {
					continue
				}
				langs, ok := cc["lang_"+config.Lang].([]interface{})
				if !ok || len(langs) == 0 {
					continue
				}
				weatherDesc, ok := cc["weatherDesc"].([]interface{})
				if !ok || len(weatherDesc) == 0 {
					continue
				}
				weatherDesc[0] = langs[0]
			}
		}
		if ws, ok := data["weather"].([]interface{}); ok {
			for _, wi := range ws {
				w, ok := wi.(map[string]interface{})
				if !ok {
					continue
				}
				if hs, ok := w["hourly"].([]interface{}); ok {
					for _, hi := range hs {
						h, ok := hi.(map[string]interface{})
						if !ok {
							continue
						}
						langs, ok := h["lang_"+config.Lang].([]interface{})
						if !ok || len(langs) == 0 {
							continue
						}
						weatherDesc, ok := h["weatherDesc"].([]interface{})
						if !ok || len(weatherDesc) == 0 {
							continue
						}
						weatherDesc[0] = langs[0]
					}
				}
			}
		}
	}
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(rv); err != nil {
		return err
	} else {
		if err = json.NewDecoder(&buf).Decode(r); err != nil {
			return err
		}
	}
	return nil
}

func getDataFromAPI() (ret resp) {
	var params []string

	if len(config.APIKey) == 0 {
		log.Fatal("No API key specified. Setup instructions are in the README.")
	}
	params = append(params, "key="+config.APIKey)

	// non-flag shortcut arguments will overwrite possible flag arguments
	for _, arg := range flag.Args() {
		if v, err := strconv.Atoi(arg); err == nil && len(arg) == 1 {
			config.Numdays = v
		} else {
			config.City = arg
		}
	}

	if len(config.City) > 0 {
		params = append(params, "q="+url.QueryEscape(config.City))
	}
	params = append(params, "format=json")
	params = append(params, "num_of_days="+strconv.Itoa(config.Numdays))
	params = append(params, "tp=3")
	if config.Lang != "" {
		params = append(params, "lang="+config.Lang)
	}

	if debug {
		fmt.Fprintln(os.Stderr, params)
	}

	res, err := http.Get(wuri + strings.Join(params, "&"))
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatal(err)
	}

	if debug {
		var out bytes.Buffer
		json.Indent(&out, body, "", "  ")
		out.WriteTo(os.Stderr)
		fmt.Println("\n")
	}

	if config.Lang == "" {
		if err = json.Unmarshal(body, &ret); err != nil {
			log.Println(err)
		}
	} else {
		if err = unmarshalLang(body, &ret); err != nil {
			log.Println(err)
		}
	}
	return
}

func init() {
	flag.IntVar(&config.Numdays, "days", 3, "Number of days of weather forecast to be displayed")
	flag.StringVar(&config.Lang, "lang", "en", "Language of the report")
	flag.StringVar(&config.City, "city", "New York", "City to be queried")
	flag.BoolVar(&debug, "debug", false, "Print out raw json response for debugging purposes")
	flag.BoolVar(&config.Imperial, "imperial", false, "Use imperial units")
	flag.BoolVar(&config.Inverse, "inverse", false, "Use inverted colors")
	flag.BoolVar(&config.Narrow, "narrow", false, "Narrow output (two columns)")
	flag.StringVar(&config.LocationName, "location_name", "", "Location name (used in the caption)")
	flag.BoolVar(&config.WindMS, "wind_in_ms", false, "Show wind speed in m/s")
	flag.BoolVar(&config.RightToLeft, "right_to_left", false, "Right to left script")
	configpath = os.Getenv("WEGORC")
	if configpath == "" {
		usr, err := user.Current()
		if err != nil {
			log.Fatalf("%v\nYou can set the environment variable WEGORC to point to your config file as a workaround.", err)
		}
		configpath = path.Join(usr.HomeDir, ".wegorc")
	}
	config.APIKey = ""
	config.Imperial = false
	config.Lang = "en"
	err := configload()
	if _, ok := err.(*os.PathError); ok {
		log.Printf("No config file found. Creating %s ...", configpath)
		if err2 := configsave(); err2 != nil {
			log.Fatal(err2)
		}
	} else if err != nil {
		log.Fatalf("could not parse %v: %v", configpath, err)
	}

	ansiEsc = regexp.MustCompile("\033.*?m")
}

func main() {
	flag.Parse()

	r := getDataFromAPI()

	if r.Data.Req == nil || len(r.Data.Req) < 1 {
		if r.Data.Err != nil && len(r.Data.Err) >= 1 {
			log.Fatal(r.Data.Err[0].Msg)
		}
		log.Fatal("Malformed response.")
	}
    locationName := r.Data.Req[0].Query
    if config.LocationName != "" {
        locationName = config.LocationName
    }
    if config.Lang == "he" || config.Lang == "ar" || config.Lang == "fa" {
        config.RightToLeft = true
    }
	if caption, ok := localizedCaption[config.Lang]; !ok {
        // r.Data.Req[0].Type, 
	    fmt.Printf("Weather report: %s\n\n", locationName)
	} else {
        if (config.RightToLeft) {
            caption = locationName + " " + caption
            space := strings.Repeat(" ", 125 - runewidth.StringWidth(caption))
            fmt.Printf("%s%s\n\n", space, caption)
        } else {
            fmt.Printf("%s %s\n\n", caption, locationName)
        }
	}
	stdout := colorable.NewColorableStdout()

	if r.Data.Cur == nil || len(r.Data.Cur) < 1 {
		log.Fatal("No weather data available.")
	}
	out := formatCond(make([]string, 5), r.Data.Cur[0], true)
	for _, val := range out {
        if config.RightToLeft {
            space := strings.Repeat(" ", 93)
            fmt.Fprintln(stdout, space, val)
        } else {
            fmt.Fprintln(stdout, val)
        }
	}

	if config.Numdays == 0 {
		return
	}
	if r.Data.Weather == nil {
		log.Fatal("No detailed weather forecast available.")
	}
	for _, d := range r.Data.Weather {
		for _, val := range printDay(d) {
			fmt.Fprintln(stdout, val)
		}
	}
}
